
function rgbToLab(rgbIn)
	local n = 0
	local rgb = {}
	for i = 1, 3 do
		local v = rgbIn[i]
		if v>0.04045 then
			rgb[i] = math.pow((v+0.055)/1.055, 2.4)
		else
			rgb[i] = v/12.92
		end
	end
	local r, g, b = unpack(rgb)
	local x = r * 0.4124 + g * 0.3576 + b * 0.1805
	local y = r * 0.2126 + g * 0.7152 + b * 0.0722
	local z = r * 0.0193 + g * 0.1192 + b * 0.9505
	
	x = x/95.047
	y = y/100
	z = z/108.883
	
	local xyz = {x, y, z}
	for i, v in ipairs(xyz) do
		if v>0.008856 then
			xyz[i] = math.pow(v, 1/3)
		else
			xyz[i] = (v*7.787) + (16/116)
		end
	end
	x, y, z = unpack(xyz)
	
	local l = (116*y) - 16
	local a = 500*(x - y)
	local b = 200*(y - z)
	
	local lab = vector{l, a, b}
	return lab
end
function GetServerColorset() -- only gets fully opaque colors
	local colorset = {}
	local numColors = 64
	for i = 1, numColors do
		local colorStr = ts.call("getColorIdTable", i-1)
		if colorStr ~= "" then
			local color = map(colorStr:split(" "), tonumber)
			assert(#color==4)
			if color[4]==1 then
				table.insert(colorset, {
					id = i-1,
					rgb = color,
					lab = rgbToLab(color),
				})
			end
		end
	end
	return colorset
end
function GetClientColorset() return GetServerColorset() end

----------------------

local function getColorFromLine(line)
	local div, rs, gs, bs, as
	if line:find("%.") then
		div = 1
		rs, gs, bs, as = line:match("^([0-9%.]+) ([0-9%.]+) ([0-9%.]+) ([0-9%.]+)$")
	else
		div = 255
		rs, gs, bs, as = line:match("^([0-9]+) ([0-9]+) ([0-9]+) ([0-9]+)$")
	end
	if not (rs and gs and bs and as) then
		print("GetColorsetFromTxt: error line \""..line.."\"")
		return nil
	end
	
	local r, g, b, a = tonumber(rs), tonumber(gs), tonumber(bs), tonumber(as)
	if not (r and g and b and a) then
		print("GetColorsetFromTxt: error line \""..line.."\"")
		return nil
	end
	return vector{r/div, g/div, b/div, a/div}
end
function GetColorsetFromTxt(text)
	local cs = {}
	local grid = {{}}
	local curDiv = 1
	local curId = 0
	for line in text:gmatch("[^\r\n]+") do
		if line:find(":") then -- div line
			curDiv = curDiv + 1
			grid[curDiv] = {}
		else -- color line
			local clr = getColorFromLine(line)
			if not clr then return nil, nil end
			table.insert(grid[curDiv], clr)
			if clr[4]==1 then table.insert(cs, {
				id = curId,
				rgb = vector{clr[1], clr[2], clr[3]},
				lab = rgbToLab(clr),
			}) end
			curId = curId + 1
		end
	end
	return cs, grid
end

----------------------

local function colorToString(color)
	return string.format("%f,%f,%f", color[1], color[2], color[3])
end
local function findClosestColor(colorRgb, colorSet)
	local colorLab = rgbToLab(colorRgb)
	local minDist = 99999
	local minDistIdx = nil
	for i, color2 in ipairs(colorSet) do
		local dist = (color2.lab - colorLab):length()
		if dist < minDist then
			minDist = dist
			minDistIdx = i
		end
	end
	color = colorSet[minDistIdx]
	return color
end

local colorMemo = {}
function GetClosestColor(colorRgb, colorSet)
	local colorRgbStr = colorToString(colorRgb)
	local color = colorMemo[colorSet] and colorMemo[colorSet][colorRgbStr]
	if not color then
		color = findClosestColor(colorRgb, colorSet)
		colorMemo[colorSet] = colorMemo[colorSet] or {}
		colorMemo[colorSet][colorRgbStr] = color
	end
	return color
end
function GetClosestColorID(colorRgb, colorSet)
	local color = GetClosestColor(colorRgb, colorSet)
	return color.id
end
